#include "League.h"
#include "PlayerManager.h"
#include "Utilities.h"
#include "ServiceProvider.h"
#include "TeamManager.h"
#include "RandomSystem.h"
#include "ZStream.h"

#include <algorithm>
#include <cassert>
#include <sstream>

#define TURBO_NEW_FEATURE_TO_VIEW_MATCH_MESSAGES_PLEASE_DO_NOT_REMOVE_ME_VOVV_LOG
#define ALL_MATCH_LOG

League::League(ServiceProvider* i_sp)
: m_sp(i_sp)
, m_calendar(i_sp)
, m_classification(i_sp)
{
	assert(i_sp);
}

League::~League()
{
	Clear();
}

void League::Clear()
{
	m_teams.clear();
	m_calendar.Clear();
	m_classification.Clear();
}

void League::Initialize(std::string& name, LeagueSettings &i_settings)
{
	m_leagueName = name;
	m_leagueName += std::string(MAX_LEAGUE_NAME_SIZE - m_leagueName.size(), ' ');
	m_settings = i_settings;
	m_teams.reserve(i_settings.numOfTeams);

	for (size_t i = 0; i < i_settings.numOfTeams; ++i)
	{
		// aggiornare in base al nuovo player manager
		Team* team = m_sp->GetTeamMgr().GetNewTeam();
		size_t budget= m_settings.minStartingTeamBdg + rand() % m_settings.deltaStartingTeamBdg;
		team->AddBudget(budget);
		m_teams.push_back(team);
	}

	m_calendar.Initialize(m_teams);
	m_classification.Initialize(m_teams);
}

void League::Update(const bool i_doNotPrompt)
{
	MatchVec currentTurn = m_calendar.GetNextTurn();
	for (MatchVecIt It = currentTurn.begin(); It != currentTurn.end(); ++It)
	{
		Match& m = *It;
		const bool produceEvents = (m.GetTeam(0)->IsUserControlled() || m.GetTeam(1)->IsUserControlled());
		m.Play(produceEvents);
		
#ifdef TURBO_NEW_FEATURE_TO_VIEW_MATCH_MESSAGES_PLEASE_DO_NOT_REMOVE_ME_VOVV_LOG
		if(!i_doNotPrompt)
			if (produceEvents)
			{
				std::cout << m.GetTeam(0)->GetName() << " - " << m.GetTeam(1)->GetName() << "\n";
				EventCollection events = m.GetEvents();
				typedef EventCollection::iterator EventIt;
				for (EventIt ev = events.begin(); ev != events.end(); ++ev)
					std::cout << (*ev).minute << " " << (*ev).message << "\n";
				std::cout << std::endl;
			}
#endif

		// update team's budget
		const int pos_sum = GetTeamPosition(m.GetTeam(0)) + GetTeamPosition(m.GetTeam(1));
		const float pos_factor = 1.0f - (static_cast<float>( pos_sum - 1 ) / (2 * m_settings.numOfTeams - 3));
		int money = m_settings.minMatchBoxOffice + static_cast<int>(static_cast<float>(m_settings.deltaMatchBoxOffice)*pos_factor);
		money = static_cast<int>(static_cast<float>(money) * RandFloatBetween(0.8f, 1.2f));
		m.GetTeam(0)->AddBudget(money);

#ifdef ALL_MATCH_LOG
		if(!i_doNotPrompt)
		{
			std::string s = " $ " + m.GetTeam(0)->GetName() + ": guadagna " + Utilities::IntToString(money) + " BANANE al botteghino.\n";
			std::cout << s;
		}
#endif

		m.GetTeam(0)->EndTurnUpdate();
		m.GetTeam(1)->EndTurnUpdate();
	}
	
	m_lastPlayedTurn = currentTurn;

	// update the classification
	m_classification.Update(currentTurn);

#ifdef ALL_MATCH_LOG
	if(!i_doNotPrompt)
	{
		std::cout << std::endl;
	}
#endif
}

void League::Restart()
{
	m_calendar.Initialize(m_teams);
	m_classification.Initialize(m_teams);
}

bool League::IsEnded() const
{
	unsigned int totTurn = (m_teams.size() - 1) * 2;
	return m_calendar.GetTurnNumber() == totTurn;
}

void League::SwapLastFirstTeam(League& o_league, const size_t team_num)
{
	assert(team_num > 0 && team_num < m_settings.numOfTeams);

	TeamVec m_teams1  = m_classification.GetSortedTeams();
	TeamVec m_teams2 = o_league.GetClassification()->GetSortedTeams();

	for(size_t i = 0; i < team_num; ++i)
	{
		Team* tmp = m_teams1[m_settings.numOfTeams - 1 - i];
		ChangeTeam(tmp, m_teams2[i]);
		o_league.ChangeTeam(m_teams2[i], tmp);
	}
}

void League::ChangeTeam(Team* i_oldTeam, Team* i_newTeam)
{
	TeamVecIt it = std::find(m_teams.begin(), m_teams.end(), i_oldTeam);

	assert(it != m_teams.end());

	*it = i_newTeam;
}

int League::GetTeamPosition(Team const * const i_team)
{
	assert(i_team);

	TeamVec list = m_classification.GetSortedTeams();

	int index = -1;
	
	for(size_t i = 0; i < list.size(); ++i)
	{
		if(i_team == list[i])
		{
			index = i;
			break;
		}
	}

	return index;
}

ZStream& League::Serialize(ZStream& o_stream)
{
	o_stream.WriteOpenTag("League");

	o_stream.WriteAttribute("TeamsNum", m_teams.size());

	TeamVecIt it = m_teams.begin();
	TeamVecIt end = m_teams.end();

	for(; it != end; ++it)
	{
		(*it)->Serialize(o_stream);
	}

	m_classification.Serialize(o_stream);
	m_calendar.Serialize(o_stream);

	o_stream.WriteCloseTag("League");

	return o_stream;
}

ZStream& League::Deserialize(ZStream& i_stream)
{
	i_stream.SkipLine();

	size_t teamsNum; 

	i_stream.ReadAttribute(teamsNum);

	for(size_t i = 0; i < teamsNum; ++i)
	{
		//Team* t = new Team(false);
		//Team* t = m_sp->GetTeamMgr().GetNewEmptyTeam();
		m_teams[i]->Deserialize(i_stream);
		//m_teams.push_back(t);
	}

	m_classification.Initialize(m_teams);
	m_calendar.Initialize(m_teams);

	m_classification.Deserialize(i_stream);
	m_calendar.Deserialize(i_stream);

	i_stream.SkipLine();

	return i_stream;
}

bool League::RemoveFirstOrLastTeams(TeamVec &o_removed, size_t i_numToRemove, bool i_removeFromEnd)
{
	TeamVec temp = m_classification.GetSortedTeams();
	if (i_removeFromEnd)
	{
		for (size_t i = 0; i < i_numToRemove; ++i)
		{
			TeamVecIt it = std::find(m_teams.begin(), m_teams.end(), temp.back());
			o_removed.push_back(*it);
			m_teams.erase(it);
			temp.pop_back();
		}
	}
	else
	{
		for (size_t i = 0; i < i_numToRemove; ++i)
		{
			TeamVecIt it = std::find(m_teams.begin(), m_teams.end(), temp.front());
			o_removed.push_back(*it);
			m_teams.erase(it);
			temp.erase(temp.begin());
		}
	}
	return true;
}

void League::EndSeasonUpdate(const bool i_bDoNotPrompt, std::stringstream& o_message)
{
	TeamVec teams = m_classification.GetSortedTeams();
		
	size_t counter = 1;
	for(TeamVecIt it = teams.begin(); it < teams.end(); ++it, ++counter)
	{
		Team& theTeam = *(*it);
		const float pos_factor = static_cast<float>(m_settings.numOfTeams - counter) / static_cast<float>(m_settings.numOfTeams - 1);
		const int money = m_settings.minEndSeasonTeamBdg + static_cast<int>(pos_factor * m_settings.deltaEndSeasonTeamBdg);

		//update budget
		theTeam.AddBudget(money);
		if(!i_bDoNotPrompt)
			o_message << theTeam.GetName() << " ha guadagnato " << money << " banane!" << std::endl;
		
		//update history
		++theTeam.GetTeamHistory().m_yearsInLeagueLayer[m_settings.layerIndex];
		if(m_settings.layerIndex == 0 && counter <= 3)
		{
			++theTeam.GetTeamHistory().m_podium[counter-1];
			++theTeam.GetTeamHistory().m_totalPodium;
		}
	}
	
	//update team points
	const TeamScoreCollection& teamScores = m_classification.GetTeamScores();
	for(TeamScoreCollection::const_iterator it= teamScores.begin(); it!= teamScores.end(); ++it)
		it->m_team->AddTeamPoints(it->m_score / (GetLayerIndex()+1));

	Restart();
}

int League::GetNumPromoted() const
{
	return m_settings.numPromoted;
}

int League::GetNumDemoted() const
{
	return m_settings.numDemoted;
}
